﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using PSE.AuditLogging.Repository.Manager;
using PSE.AuditLogging.Client.Configuration;
using PSE.AuditLogging.Common.Configuration;
using PSE.Framework.ProviderLoader.Configuration;
using PSE.AuditLogging.LogEntry;
using System.Diagnostics;
using PSE.AuditLogging.Common.Exception;
using System.Configuration;
using PSE.AuditLogging.Client;
using PSE.Framework.ErrorLogging;

namespace PSE.AuditLogging.ContingencyService
{
    internal class LogResender : IDisposable
    {
        internal bool IsRunning
        {
            get;
            private set;
        }

        internal string ProductId
        {
            get;
            private set;
        }

        private ManualResetEvent _executionControl;
        private Thread _execution;
        private Dictionary<string, TemporaryRepositoryManager> _temporaryRepositoriesForStorage;//StorageId x TemporaryRepository
        private Dictionary<string, List<ContingencyRepositoryManager>> _contingencyRepositoriesForStorage;//StorageId x ContingencyRepository

        private int ExecutionInterval
        {
            get
            {
                return ConfigurationReader.ContingencyServiceConfiguration.ExecutionInterval;
            }
        }
        private LogSenderConfigurationSection Configuration
        {
            get
            {
                LogSenderConfigurationSection ret = ConfigurationReader.ContingencyServiceConfiguration.LogSenders[this.ProductId];

                if (ret == null)
                    throw new ConfigurationErrorsException(string.Format("The product '{0}' its not configured.", this.ProductId));

                return ret;
            }
        }

        internal LogResender(string productId)
        {
            this.ProductId = productId;
            this.IsRunning = false;
        }

        internal bool Start()
        {
            try
            {
                //ler as configuracoes de acordo com o produto
                _executionControl = new ManualResetEvent(false);

                LoadProviders();

                //setar a configuracao que o client de envio de mensagens ira utilizar
                PSE.AuditLogging.Client.ConfigurationReader.LogClientConfiguration = Configuration;

                _execution = new Thread(DoWork);
                _execution.Start();

                this.IsRunning = true;

                return true;
            }
            catch (Exception ex)
            {
                this.IsRunning = false;
                //somente disparar evento notificando o error
                ErrorLogger.WriteLog(LogType.Error, "A critical error occurs. The component will be stoped." + Environment.NewLine + ex.ToString());

                return false;
            }
        }

        internal bool Stop()
        {
            try
            {
                _executionControl.Set();
                _execution.Join();

                return true;
            }
            catch (Exception ex)
            {
                //somente disparar evento notificando o error
                ErrorLogger.WriteLog(LogType.Error, "A critical error occurs. The component will be stoped." + Environment.NewLine + ex.ToString());

                return false;
            }
            finally
            {
                this.IsRunning = false;
            }
        }

        internal bool Restart()
        {
            bool ret = true;

            if (!this.Stop())
                ret = false;

            if (!this.Start())
                ret = false;

            return ret;
        }

        private void DoWork()
        {
            LogEntryPackage entryPackage;
            TemporaryRepositoryManager temporaryRepository;

            //loop 0 - controle de execucao de acordo com o periodo configurado
            while (!_executionControl.WaitOne(ExecutionInterval))
            {
                try
                {
                    entryPackage = null;

                    //loop 1 - varrer a lista de storages
                    foreach (KeyValuePair<string, List<ContingencyRepositoryManager>> contingencyRepositories in _contingencyRepositoriesForStorage)
                    {
                        //caso tenha sido sinalizado o final da execucao, parar o loop
                        if (_executionControl.WaitOne(0))
                            break;

                        //recuperar o manager do repositorio temporario relacionado ao storage
                        temporaryRepository = _temporaryRepositoriesForStorage[contingencyRepositories.Key];

                        //loop 2 - varrer a lista dos repositorios de contingencia
                        foreach (ContingencyRepositoryManager contingencyRepository in contingencyRepositories.Value)
                        {
                            //caso tenha sido sinalizado o final da execucao, parar o loop
                            if (_executionControl.WaitOne(0))
                                break;

                            //loop 2 - recuperar logs armazenados no repositorio de contingencia enquanto houver algo armazenado
                            do
                            {
                                //caso tenha sido sinalizado o final da execucao, parar o loop
                                if (_executionControl.WaitOne(0))
                                    break;

                                #region ler um pacote do repositorio temporario
                                try
                                {
                                    //recuperar um log do repositorio de contingencia
                                    entryPackage = contingencyRepository.Peek();

                                    if (entryPackage == null) //se nao ha mensagens no repositorio
                                        break;//parar loop e passar para proximo repositorio
                                }
                                catch (InvalidLogEntryPackageException ex)
                                {///WARNING
                                    ///Situacao: Existe um objeto invalido no repositorio de contingencia
                                    ///Probabilidade: Baixa
                                    ///* Logar e remover o objeto
                                    ErrorLogger.WriteLog(LogType.Warning, "An invalid log entry package will be discarded." + Environment.NewLine + ex.ToString());

                                    //Remover log do repositorio de contingencia, caso ocorra algum erro na remocao, parar a execucao
                                    //descatar log invalido armazenado no repositorio
                                    contingencyRepository.RemoveTop();

                                    //parar o loop interno
                                    break;
                                }
                                #endregion

                                //caso tenha sido sinalizado o final da execucao, parar o loop
                                if (_executionControl.WaitOne(0))
                                    break;

                                #region armazenar um log no repositorio temporario
                                //reenviar o pacote
                                LogSender.Resend(entryPackage);

                                //Remover log do repositorio de contingencia, caso ocorra algum erro na remocao, parar a execucao
                                contingencyRepository.RemoveTop();
                                #endregion

                            } while (entryPackage != null);
                        }
                    }
                }
                //capturar exceptions nao tratadas
                catch (Exception ex)
                {///ERROR
                    ///Situacao: ocorreu um erro nao tratado                    
                    ///* logar e continuar a execucao
                    ErrorLogger.WriteLog(LogType.Error, ex);

                    continue;
                }
            }

            this.IsRunning = false;
        }

        private void LoadProviders()
        {
            List<ContingencyRepositoryManager> items;

            _temporaryRepositoriesForStorage = new Dictionary<string, TemporaryRepositoryManager>();
            _contingencyRepositoriesForStorage = new Dictionary<string, List<ContingencyRepositoryManager>>();

            foreach (StorageConfigurationElement storage in Configuration.Storages)
            {
                //criar repositorio temporario para a instancia
                TemporaryRepositoryManager temporaryRepositoryManager = new TemporaryRepositoryManager();
                temporaryRepositoryManager.LoadProvider(storage.TemporaryRepositoryProvider);

                items = new List<ContingencyRepositoryManager>();

                foreach (ProviderConfigurationElement contingencyRepositoryProvider in storage.ContingencyRepositoriesProviders)
                {
                    ContingencyRepositoryManager contingencyRepositoryManager = new ContingencyRepositoryManager();
                    contingencyRepositoryManager.LoadProvider(contingencyRepositoryProvider);
                    items.Add(contingencyRepositoryManager);
                }

                _temporaryRepositoriesForStorage.Add(storage.Id, temporaryRepositoryManager);
                _contingencyRepositoriesForStorage.Add(storage.Id, items);
            }
        }

        #region IDisposable Members
        public void Dispose()
        {
            this.Stop();
        }
        #endregion
    }
}
